// grepWin - regex search and replace for Windows

// Copyright (C) 2007-2010, 2012-2017, 2019-2021 - Stefan Kueng

// This program is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License
// as published by the Free Software Foundation; either version 2
// of the License, or (at your option) any later version.

// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software Foundation,
// 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
//
#include "stdafx.h"
#include "resource.h"
#include "maxpath.h"
#include "BookmarksDlg.h"
#include "NameDlg.h"
#include "Theme.h"
#include "ResString.h"
#include <string>

#pragma warning(push)
#pragma warning(disable : 4996) // warning STL4010: Various members of std::allocator are deprecated in C++17
#include <boost/regex.hpp>
#pragma warning(pop)

CBookmarksDlg::CBookmarksDlg(HWND hParent)
    : m_hParent(hParent)
    , m_bUseRegex(false)
    , m_bCaseSensitive(false)
    , m_bDotMatchesNewline(false)
    , m_bBackup(false)
    , m_bWholeWords(false)
    , m_bUtf8(false)
    , m_bForceBinary(false)
    , m_bIncludeSystem(false)
    , m_bIncludeFolder(false)
    , m_bIncludeHidden(false)
    , m_bIncludeBinary(false)
    , m_bFileMatchRegex(false)
    , m_themeCallbackId(0)
{
}

CBookmarksDlg::~CBookmarksDlg()
{
}

LRESULT CBookmarksDlg::DlgFunc(HWND hwndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
    UNREFERENCED_PARAMETER(lParam);
    switch (uMsg)
    {
        case WM_INITDIALOG:
        {
            m_themeCallbackId = CTheme::Instance().RegisterThemeChangeCallback(
                [this]() {
                    CTheme::Instance().SetThemeForDialog(*this, CTheme::Instance().IsDarkTheme());
                });
            CTheme::Instance().SetThemeForDialog(*this, CTheme::Instance().IsDarkTheme());
            InitDialog(hwndDlg, IDI_GREPWIN);
            CLanguage::Instance().TranslateWindow(*this);
            // initialize the controls
            InitBookmarks();

            m_resizer.Init(hwndDlg);
            m_resizer.UseSizeGrip(!CTheme::Instance().IsDarkTheme());
            m_resizer.AddControl(hwndDlg, IDC_INFOLABEL, RESIZER_TOPLEFTRIGHT);
            m_resizer.AddControl(hwndDlg, IDC_BOOKMARKS, RESIZER_TOPLEFTBOTTOMRIGHT);
            m_resizer.AddControl(hwndDlg, IDOK, RESIZER_BOTTOMRIGHT);
            m_resizer.AddControl(hwndDlg, IDCANCEL, RESIZER_BOTTOMRIGHT);

            WINDOWPLACEMENT wpl  = {0};
            DWORD           size = sizeof(wpl);
            if (SHGetValue(HKEY_CURRENT_USER, L"Software\\grepWin", L"windowposBookmarks", REG_NONE, &wpl, &size) == ERROR_SUCCESS)
                SetWindowPlacement(*this, &wpl);
            else
                ShowWindow(*this, SW_SHOW);
        }
            return TRUE;
        case WM_COMMAND:
            return DoCommand(LOWORD(wParam), HIWORD(wParam));
        case WM_SIZE:
        {
            m_resizer.DoResize(LOWORD(lParam), HIWORD(lParam));
        }
        break;
        case WM_GETMINMAXINFO:
        {
            MINMAXINFO* mmi       = reinterpret_cast<MINMAXINFO*>(lParam);
            mmi->ptMinTrackSize.x = m_resizer.GetDlgRect()->right;
            mmi->ptMinTrackSize.y = m_resizer.GetDlgRect()->bottom;
            return 0;
        }
        case WM_CONTEXTMENU:
        {
            long x            = GET_X_LPARAM(lParam);
            long y            = GET_Y_LPARAM(lParam);
            HWND hListControl = GetDlgItem(*this, IDC_BOOKMARKS);
            if (reinterpret_cast<HWND>(wParam) == hListControl)
            {
                int nCount = ListView_GetItemCount(hListControl);
                if (nCount == 0)
                    break;
                int iItem = ListView_GetSelectionMark(hListControl);
                if (iItem < 0)
                    break;

                POINT pt = {x, y};
                if ((x == -1) && (y == -1))
                {
                    RECT rc;
                    ListView_GetItemRect(hListControl, iItem, &rc, LVIR_LABEL);
                    pt.x = (rc.right - rc.left) / 2;
                    pt.y = (rc.bottom - rc.top) / 2;
                    ClientToScreen(hListControl, &pt);
                }
                HMENU hMenu  = LoadMenu(hResource, MAKEINTRESOURCE(IDC_BKPOPUP));
                HMENU hPopup = GetSubMenu(hMenu, 0);
                CLanguage::Instance().TranslateMenu(hPopup);
                TrackPopupMenu(hPopup, TPM_LEFTALIGN | TPM_RIGHTBUTTON, x, y, 0, *this, nullptr);
            }
        }
        break;
        case WM_NOTIFY:
        {
            if (wParam == IDC_BOOKMARKS)
            {
                LPNMITEMACTIVATE lpnmitem = reinterpret_cast<LPNMITEMACTIVATE>(lParam);
                if (lpnmitem->hdr.code == NM_DBLCLK)
                {
                    PrepareSelected();
                    SendMessage(m_hParent, WM_BOOKMARK, 0, 0);
                }
            }
        }
        break;
        case WM_CLOSE:
            CTheme::Instance().RemoveRegisteredCallback(m_themeCallbackId);
            break;
        default:
            return FALSE;
    }
    return FALSE;
}

LRESULT CBookmarksDlg::DoCommand(int id, int /*msg*/)
{
    switch (id)
    {
        case IDOK:
        {
            PrepareSelected();
        }
            [[fallthrough]];
        case IDCANCEL:
        {
            WINDOWPLACEMENT wpl = {0};
            wpl.length          = sizeof(WINDOWPLACEMENT);
            GetWindowPlacement(*this, &wpl);
            SHSetValue(HKEY_CURRENT_USER, L"Software\\grepWin", L"windowposBookmarks", REG_NONE, &wpl, sizeof(wpl));
            if ((id == IDOK) && (ListView_GetNextItem(GetDlgItem(*this, IDC_BOOKMARKS), -1, LVNI_SELECTED) >= 0))
                SendMessage(m_hParent, WM_BOOKMARK, 0, 0);
            ShowWindow(*this, SW_HIDE);
        }
        break;
        case ID_REMOVEBOOKMARK:
        {
            int iItem = ListView_GetNextItem(GetDlgItem(*this, IDC_BOOKMARKS), -1, LVNI_SELECTED);
            if (iItem >= 0)
            {
                auto   buf    = std::make_unique<wchar_t[]>(MAX_PATH_NEW);
                LVITEM lv     = {0};
                lv.iItem      = iItem;
                lv.mask       = LVIF_TEXT;
                lv.pszText    = buf.get();
                lv.cchTextMax = MAX_PATH_NEW;
                ListView_GetItem(GetDlgItem(*this, IDC_BOOKMARKS), &lv);
                m_bookmarks.RemoveBookmark(buf.get());
                ListView_DeleteItem(GetDlgItem(*this, IDC_BOOKMARKS), iItem);
            }
        }
        break;
        case ID_RENAMEBOOKMARK:
        {
            int iItem = ListView_GetNextItem(GetDlgItem(*this, IDC_BOOKMARKS), -1, LVNI_SELECTED);
            if (iItem >= 0)
            {
                auto   buf    = std::make_unique<wchar_t[]>(MAX_PATH_NEW);
                LVITEM lv     = {0};
                lv.iItem      = iItem;
                lv.mask       = LVIF_TEXT;
                lv.pszText    = buf.get();
                lv.cchTextMax = MAX_PATH_NEW;
                ListView_GetItem(GetDlgItem(*this, IDC_BOOKMARKS), &lv);
                CNameDlg nameDlg(*this);
                nameDlg.SetName(buf.get());
                if (nameDlg.DoModal(hResource, IDD_NAME, *this) == IDOK)
                {
                    if (nameDlg.GetName().compare(buf.get()))
                    {
                        Bookmark bk = m_bookmarks.GetBookmark(buf.get());
                        RemoveQuotes(bk.Search);
                        RemoveQuotes(bk.Replace);
                        bk.Name = nameDlg.GetName();
                        m_bookmarks.AddBookmark(bk);
                        m_bookmarks.RemoveBookmark(buf.get());
                        m_bookmarks.Save();
                        InitBookmarks();
                    }
                }
            }
        }
        break;
        default:
            break;
    }
    return 1;
}

void CBookmarksDlg::InitBookmarks()
{
    HWND  hListControl = GetDlgItem(*this, IDC_BOOKMARKS);
    DWORD exStyle      = LVS_EX_DOUBLEBUFFER | LVS_EX_FULLROWSELECT;
    ListView_DeleteAllItems(hListControl);

    int c = Header_GetItemCount(ListView_GetHeader(hListControl)) - 1;
    while (c >= 0)
        ListView_DeleteColumn(hListControl, c--);

    ListView_SetExtendedListViewStyle(hListControl, exStyle);

    std::wstring sName          = TranslatedString(hResource, IDS_NAME);
    std::wstring sSearchString  = TranslatedString(hResource, IDS_SEARCHSTRING);
    std::wstring sReplaceString = TranslatedString(hResource, IDS_REPLACESTRING);

    LVCOLUMN lvc = {0};
    lvc.mask     = LVCF_TEXT;
    lvc.fmt      = LVCFMT_LEFT;
    lvc.cx       = -1;
    lvc.pszText  = const_cast<LPWSTR>(static_cast<LPCWSTR>(sName.c_str()));
    ListView_InsertColumn(hListControl, 0, &lvc);
    lvc.pszText = const_cast<LPWSTR>(static_cast<LPCWSTR>(sSearchString.c_str()));
    ListView_InsertColumn(hListControl, 1, &lvc);
    lvc.pszText = const_cast<LPWSTR>(static_cast<LPCWSTR>(sReplaceString.c_str()));
    ListView_InsertColumn(hListControl, 2, &lvc);

    m_bookmarks.Load();
    CSimpleIni::TNamesDepend sections;
    m_bookmarks.GetAllSections(sections);
    for (CSimpleIni::TNamesDepend::iterator it = sections.begin(); it != sections.end(); ++it)
    {
        std::wstring searchString  = m_bookmarks.GetValue(*it, L"searchString", L"");
        std::wstring replaceString = m_bookmarks.GetValue(*it, L"replaceString", L"");
        RemoveQuotes(searchString);
        RemoveQuotes(replaceString);

        LVITEM lv = {0};
        lv.mask   = LVIF_TEXT;
        auto pBuf = std::make_unique<wchar_t[]>(wcslen(*it) + 1);
        wcscpy_s(pBuf.get(), wcslen(*it) + 1, *it);
        lv.pszText = pBuf.get();
        lv.iItem   = ListView_GetItemCount(hListControl);
        int ret    = ListView_InsertItem(hListControl, &lv);
        if (ret >= 0)
        {
            lv.iItem    = ret;
            lv.iSubItem = 1;
            pBuf        = std::make_unique<wchar_t[]>(searchString.size() + 1);
            lv.pszText  = pBuf.get();
            wcscpy_s(lv.pszText, searchString.size() + 1, searchString.c_str());
            ListView_SetItem(hListControl, &lv);
            lv.iSubItem = 2;
            pBuf        = std::make_unique<wchar_t[]>(replaceString.size() + 1);
            lv.pszText  = pBuf.get();
            wcscpy_s(lv.pszText, replaceString.size() + 1, replaceString.c_str());
            ListView_SetItem(hListControl, &lv);
        }
    }

    ListView_SetColumnWidth(hListControl, 0, LVSCW_AUTOSIZE_USEHEADER);
    ListView_SetColumnWidth(hListControl, 1, LVSCW_AUTOSIZE_USEHEADER);
    ListView_SetColumnWidth(hListControl, 2, LVSCW_AUTOSIZE_USEHEADER);
}

void CBookmarksDlg::RemoveQuotes(std::wstring& str)
{
    if (!str.empty())
    {
        if (str[0] == '"')
            str = str.substr(1);
        if (!str.empty())
        {
            if (str[str.size() - 1] == '"')
                str = str.substr(0, str.size() - 1);
        }
    }
}

void CBookmarksDlg::PrepareSelected()
{
    m_bookmarks.Save();
    int iItem = ListView_GetNextItem(GetDlgItem(*this, IDC_BOOKMARKS), -1, LVNI_SELECTED);
    if (iItem >= 0)
    {
        auto   buf    = std::make_unique<wchar_t[]>(MAX_PATH_NEW);
        LVITEM lv     = {0};
        lv.iItem      = iItem;
        lv.mask       = LVIF_TEXT;
        lv.pszText    = buf.get();
        lv.cchTextMax = MAX_PATH_NEW;
        ListView_GetItem(GetDlgItem(*this, IDC_BOOKMARKS), &lv);
        m_searchString  = m_bookmarks.GetValue(buf.get(), L"searchString", L"");
        m_path          = m_bookmarks.GetValue(buf.get(), L"searchpath", L"");
        m_replaceString = m_bookmarks.GetValue(buf.get(), L"replaceString", L"");
        m_sExcludeDirs  = m_bookmarks.GetValue(buf.get(), L"excludedirs", L"");
        m_sFileMatch    = m_bookmarks.GetValue(buf.get(), L"filematch", L"");
        RemoveQuotes(m_searchString);
        RemoveQuotes(m_replaceString);
        RemoveQuotes(m_sExcludeDirs);
        RemoveQuotes(m_sFileMatch);
        m_bUseRegex          = wcscmp(m_bookmarks.GetValue(buf.get(), L"useregex", L"false"), L"true") == 0;
        m_bCaseSensitive     = wcscmp(m_bookmarks.GetValue(buf.get(), L"casesensitive", L"false"), L"true") == 0;
        m_bDotMatchesNewline = wcscmp(m_bookmarks.GetValue(buf.get(), L"dotmatchesnewline", L"false"), L"true") == 0;
        m_bBackup            = wcscmp(m_bookmarks.GetValue(buf.get(), L"backup", L"false"), L"true") == 0;
        m_bWholeWords        = wcscmp(m_bookmarks.GetValue(buf.get(), L"wholewords", L"false"), L"true") == 0;
        m_bUtf8              = wcscmp(m_bookmarks.GetValue(buf.get(), L"utf8", L"false"), L"true") == 0;
        m_bForceBinary       = wcscmp(m_bookmarks.GetValue(buf.get(), L"binary", L"false"), L"true") == 0;
        m_bIncludeSystem     = wcscmp(m_bookmarks.GetValue(buf.get(), L"includesystem", L"false"), L"true") == 0;
        m_bIncludeFolder     = wcscmp(m_bookmarks.GetValue(buf.get(), L"includefolder", L"false"), L"true") == 0;
        m_bIncludeHidden     = wcscmp(m_bookmarks.GetValue(buf.get(), L"includehidden", L"false"), L"true") == 0;
        m_bIncludeBinary     = wcscmp(m_bookmarks.GetValue(buf.get(), L"includebinary", L"false"), L"true") == 0;
        m_bFileMatchRegex    = wcscmp(m_bookmarks.GetValue(buf.get(), L"filematchregex", L"false"), L"true") == 0;
    }
}
